<?php

namespace app\index\common\logchecker\Scorers;

use app\index\common\logchecker\Parsers\EacParser;

class EacScorer extends BaseScorer
{
	/**
	 * Creates a new EacParser object based on the log file provided, and parses it.
	 *
	 * RipLogChecker constructor.
	 *
	 * @param string $log
	 */
	public function __construct($log)
	{
		/* Load the log file */
		$this->log = $log;
		$this->parser = new EacParser($log);
		$this->errors = [];
		/* Initialize deducted points */
		$this->setDeductedPoints(0);
	}

	/**
	 * Parses the log file, and returns false if it
	 * fails to parse the log file.
	 *
	 * @return bool
	 */
	public function score()
	{
		/* If the log is empty, return false */
		if (!$this->log) {
			return FALSE;
		}

		/* Parse $this->log, and return false if even one check fails
		 * TODO: refactor this into something nicer if possible
		 */
		if (!$this->checkReadMode()) {
			return FALSE;
		}
		if (!$this->checkDefeatAudioCache()) {
			return FALSE;
		}
		if (!$this->checkC2PointersUsed()) {
			return FALSE;
		}
		if (!$this->checkFillUpOffsetSamples()) {
			return FALSE;
		}
		if (!$this->checkSilentBlockDeletion()) {
			return FALSE;
		}
		if (!$this->checkNullSamplesUsed()) {
			return FALSE;
		}
		if (!$this->checkGapHandling()) {
			return FALSE;
		}
		if (!$this->checkID3TagsAdded()) {
			return FALSE;
		}
		if (!$this->checkCRCMismatch()) {
			return FALSE;
		}
		if (!$this->checkTestCopyUsed()) {
			return FALSE;
		}

		/* Log parsed successfully, return true */

		return TRUE;
	}

	/**
	 * Check whether the read mode is secure or insecure,
	 * set the $deductedPoints and return false if it fails
	 * to check the read mode.
	 *
	 * @return bool
	 */
	protected function checkReadMode()
	{
		$assertion = 'Secure';
		$result = $this->parser->data['options']['drive_options']['read_mode'];
		$check = self::INSECURE_MODE_USED;

		return $this->scoreResult($result, $assertion, $check);
	}

	/**
	 * Check if "Defeat audio cache" is set to yes,
	 * set the $deductedPoints and return false if it fails
	 * to check the read mode.
	 *
	 * @return bool
	 */
	protected function checkDefeatAudioCache()
	{
		$assertion = TRUE;
		$result = $this->parser->data['options']['drive_options']['defeat_audio_cache'];
		$check = self::DEFEAT_AUDIO_CACHE_DISABLED;

		return $this->scoreResult($result, $assertion, $check);
	}

	/**
	 * Check if C2 pointers is set to no,
	 * set the $deductedPoints and return false if it fails
	 * to check the read mode.
	 *
	 * @return bool
	 */
	protected function checkC2PointersUsed()
	{
		$assertion = FALSE;
		$result = $this->parser->data['options']['drive_options']['make_use_of_c2_pointers'];
		$check = self::C2_POINTERS_USED;

		return $this->scoreResult($result, $assertion, $check);
	}

	/**
	 * Check whether "Fill up missing offset samples with silence"
	 * is set to yes and set the $deductedPoints and return false
	 * if it fails.
	 *
	 * @return bool
	 */
	protected function checkFillUpOffsetSamples()
	{
		$assertion = TRUE;
		$result = $this->parser->data['options']['read_options']['fill_up_missing_offset_samples_with_silence'];
		$check = self::DOES_NOT_FILL_MISSING_SAMPLES;

		return $this->scoreResult($result, $assertion, $check);
	}

	/**
	 * Check if "Delete leading and trailing silent blocks"
	 * is set to "No" and set the $deductedPoints and return
	 * false if it fails.
	 *
	 * @return bool
	 */
	protected function checkSilentBlockDeletion()
	{
		$assertion = FALSE;
		$result = $this->parser->data['options']['read_options']['delete_leading_trailing_silent_blocks'];
		$check = self::DELETES_SILENT_BLOCKS;

		return $this->scoreResult($result, $assertion, $check);
	}

	/**
	 * Check if Null samples are used in CRC calculations,
	 * set the $deductedPoints and return false if it fails.
	 *
	 * @return bool
	 */
	protected function checkNullSamplesUsed()
	{
		$assertion = TRUE;
		$result = $this->parser->data['options']['read_options']['null_samples_used_in_crc_calculations'];
		$check = self::NULL_SAMPLES_NOT_USED;

		return $this->scoreResult($result, $assertion, $check);
	}

	/**
	 * Check whether gap handling was detected
	 * and if the correct mode was used, then set
	 * $deductedPoints. Returns false on failure;.
	 *
	 * @return bool
	 */
	protected function checkGapHandling()
	{
		$assertion = 'Appended to previous track';
		$result = $this->parser->data['options']['read_options']['gap_handling'];
		$check = self::GAP_HANDLING_NOT_DETECTED;

		return $this->scoreResult($result, $assertion, $check);
	}

	/**
	 * Check whether ID3 tags were added. Set
	 * $deductedPoints and return false on failure.
	 *
	 * @return bool
	 */
	protected function checkID3TagsAdded()
	{
		$assertion = FALSE;
		$result = $this->parser->data['options']['output_options']['add_id3_tag'];
		$check = self::ID3_TAGS_ADDED;

		return $this->scoreResult($result, $assertion, $check);
	}

	/**
	 * Check if there are CRC mismatches in the log.
	 * Set $deductedPoints and return false on failure.
	 *
	 * @return bool
	 */
	protected function checkCRCMismatch()
	{
		/* Initialize arrays */
		$testCRCs = [];
		$copyCRCs = [];

		/* Save Test CRCs into array */
		foreach ($this->parser->data['tracks'] as $match) {
			$testCrc = $match['checksums']['test_crc'];
			array_push($testCRCs, $testCrc);
			$copyCrc = $match['checksums']['copy_crc'];
			array_push($copyCRCs, $copyCrc);
		}

		/* Compare the arrays */

		$result = array_diff($testCRCs, $copyCRCs);

		/* If the array diff is empty, there are no mismatching CRCs*/
		/* TODO: Save a list of mismatching CRCs for error reporting */

		if ($result == NULL) {
			$this->errors[self::CRC_MISMATCH] = FALSE;

			return TRUE;
		} elseif ($result != NULL) {
			/* TODO: Report error for every CRC mismatch */
			$this->errors[self::CRC_MISMATCH] = TRUE;
			/* TODO: Deduct points for every mismatch */
			$this->deductedPoints += parent::$pointDeductions[self::CRC_MISMATCH];

			return TRUE;
		}

		/* Something went wrong! */

		return FALSE;
	}

	/**
	 * Check if Test & Copy is used, and deduct points if not.
	 * Return false on failure.
	 *
	 * @return bool
	 */
	protected function checkTestCopyUsed()
	{
		foreach ($this->parser->data['tracks'] as $track) {
			if ($track['copy'] != 'OK') {
				$this->errors[self::TEST_COPY_NOT_USED] = TRUE;
				$this->deductedPoints += parent::$pointDeductions[self::TEST_COPY_NOT_USED];
			}
		}

		if (!isset($this->errors[self::TEST_COPY_NOT_USED])) {
			$this->errors[self::TEST_COPY_NOT_USED] = FALSE;
		}

		return TRUE;
	}

	/**
	 * Check whether result equals the given assertion,
	 * and if not deduct points according to the given
	 * check.
	 *
	 * @param $result
	 * @param $assertion
	 * @param $check
	 *
	 * @return bool
	 */
	protected function scoreResult($result, $assertion, $check)
	{
		if ($result == $assertion) {
			$this->errors[$check] = FALSE;

			return TRUE;
		} else {
			$this->errors[$check] = TRUE;
			$this->deductedPoints += self::$pointDeductions[$check];

			return TRUE;
		}
	}
}
